/**
 *    Copyright 2012 Geensoft S.A.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
package ar.com.greensoft.neo.dao.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.richfaces.model.ArrangeableState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import ar.com.greensoft.neo.dao.DaoException;
import ar.com.greensoft.neo.dao.UserDao;
import ar.com.greensoft.neo.model.Authority;
import ar.com.greensoft.neo.model.AuthorityId;
import ar.com.greensoft.neo.model.User;
import ar.com.greensoft.neo.model.User_;
import ar.com.greensoft.neo.search.SearchInput;
import ar.com.greensoft.neo.search.TextSearchInput;
import ar.com.greensoft.neo.util.SearchUtil;

/**
*
* @author Jorge E. Villaverde
* @since 1.0
*/
@Transactional
@Repository
public class JpaUserDaoImpl implements UserDao {
	
	@PersistenceContext
	private EntityManager entityManager;
	
	protected static final Logger logger = LoggerFactory.getLogger(JpaUserDaoImpl.class);

	@Override
	public User findUserByUsername(String username) throws DaoException {
		username = SearchUtil.likeString(username); 
		
		CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
		
		CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
		Root<User> root = criteriaQuery.from(User.class);
		
		criteriaQuery.select(root);
		
		criteriaQuery.where(
				criteriaBuilder.or(
						criteriaBuilder.like(root.get(User_.username), username)));
					
		TypedQuery<User> typedQuery = entityManager.createQuery(criteriaQuery);
		
		try {
			return typedQuery.getSingleResult();
		} catch (NoResultException e) {
			logger.error("Error finding " + getClass().getSimpleName());
		} catch (NonUniqueResultException e) {
			logger.error("Error finding " + getClass().getSimpleName());
		}
		return null;
	}

	@Override
	public void addAuthority(Authority authority) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Granting Authority: " + authority.getAuthority() + " to User: " + authority.getUser().getUsername());
		entityManager.persist(authority);		
	}

	@Override
	public User findById(String username) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Returning User with username: " + username);
		return entityManager.find(User.class, username);
	}

	@Override
	public List<User> findAll() throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Returning the list of Users");
		TypedQuery<User> query = entityManager.createNamedQuery(User.FIND_ALL_QUERY_NAME, User.class);	
		return query.getResultList();
	}

	@Override
	public List<User> findEntries(int startPosition, int maxResult) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Returning the list of Users from: " + startPosition + " to " + (startPosition+maxResult));

		TypedQuery<User> query = entityManager.createNamedQuery(User.FIND_ALL_QUERY_NAME, User.class);

		query.setFirstResult(startPosition);
		query.setMaxResults(maxResult);
		
		return query.getResultList();
	}

	@Override
	public User merge(User persistentObject) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Merging User: " + persistentObject);
		User mergeEntity = entityManager.merge(persistentObject);
		entityManager.flush();
		return mergeEntity; 
	}

	@Override
	public void persist(User transientObject) throws DaoException {
		entityManager.persist(transientObject);
	}

	@Override
	public void refresh(User persistentObject) throws DaoException {
		entityManager.refresh(persistentObject);
	}

	@Override
	public void remove(User persistentObject) throws DaoException {
		User entity = entityManager.merge(persistentObject);
		entityManager.remove(entity);
	}

	@Override
	public long count() throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Counting Users in the Repository");
		return (Long)entityManager.createNamedQuery(User.COUNT_QUERY_NAME).getSingleResult();
	}

	@Override
	public List<User> findEntriesBySearchInput(SearchInput input, int startPosition, int maxResult) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Returning the list of Entities from: " + startPosition + " to " + (startPosition+maxResult) + " by SeachInput");

		CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
		
		CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
		Root<User> root = criteriaQuery.from(User.class);
		
		criteriaQuery.select(root);
		Expression<Boolean> where = createWhereFromSearchInput(input, root);
		if(where != null)
			criteriaQuery.where(where);

		TypedQuery<User> typedQuery = entityManager.createQuery(criteriaQuery);
		typedQuery.setFirstResult(startPosition);
		typedQuery.setMaxResults(maxResult);
		
		return typedQuery.getResultList();
	}

	@Override
	public long countBySearchInput(SearchInput input) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Counting Entities in the Repository by Search Input");
		
		CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
		CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);

		Root<User> root = criteriaQuery.from(User.class);
		
		criteriaQuery.select(criteriaBuilder.count(root));
		Expression<Boolean> where = createWhereFromSearchInput(input, root);
		if(where != null)
			criteriaQuery.where(where);
		
		TypedQuery<Long> typedQuery = entityManager.createQuery(criteriaQuery);
		
		return typedQuery.getSingleResult();
	}

	@Override
	public void removeAuthority(Authority authority) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Removing Authority: " + authority.getAuthority() + " from User: " + authority.getUser().getUsername());
		entityManager.remove(authority);		
	}

	@Override
	public void removeAuthority(User user, String authority) throws DaoException {
		if(logger.isTraceEnabled())
			logger.trace("Removing Authority: " + authority + " from User: " + user.getUsername());
		
		AuthorityId id = new AuthorityId();
		id.setUser(user);
		id.setAuthority(authority);
		
		Authority auth = entityManager.find(Authority.class, id);
		
		entityManager.remove(auth);
	}
	
	protected Expression<Boolean> createWhereFromSearchInput(SearchInput input, Root<User> root) {
		TextSearchInput tsi = (TextSearchInput)input;
		
		if(tsi == null)
			return null;
		if(!StringUtils.hasText(tsi.getText()))		
			return null;

		String searchText = SearchUtil.likeString(tsi.getText());
		CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
		
		Predicate predicate = criteriaBuilder.like(
				root.get(User_.username), searchText);
		
		return predicate;
	}

	@Override
	public List<User> findEntries(ArrangeableState state, int startPosition,
			int maxResult) throws DaoException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long countByArrangeableState(ArrangeableState state)
			throws DaoException {
		// TODO Auto-generated method stub
		return 0;
	}	
}
